/**
 * 
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 *   
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * 
 */
package org.jiopi.ibean.kernel.repository;

import java.io.File;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;

import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.log4j.Logger;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.jiopi.framework.core.Config;
import org.jiopi.framework.core.JiopiConfigConstants;
import org.jiopi.ibean.kernel.KernelConstants;
import org.jiopi.ibean.kernel.NameVersion;
import org.jiopi.ibean.kernel.repository.config.ModuleConfig;
import org.jiopi.ibean.kernel.util.FileUtil;
import org.jiopi.ibean.kernel.util.ResourceUtil;

/**
 * 
 * 配置资源库管理类
 *
 */
public class ResourcePool implements Comparable<ResourcePool>{
	
	private static Logger logger = Logger.getLogger(ResourcePool.class);
	
	private static String MODULE_NAME = "\\$\\{module\\}";
	
	private static String BLUEPRINT_NAME = "\\$\\{blueprint\\}";
	
	
	/**
	 * 配置资源名
	 */
	private final String name;
	/**
	 * 优先级
	 */
	private final int pri;
	
	/**
	 * 资源库的访问验证信息,可以 null
	 */
	protected final UsernamePasswordCredentials creds;
	
	/**
	 * 资源库的模块查找路径
	 */
	private final String modulePath;
	
	private final String blueprintPath;
	
	private final File poolCacheDir;
	
	private final boolean isLocal;
	
	/**
	 * <group,<fileName,URL>>
	 */
	private final HashMap<String,HashMap<String,URL>> commonLibs = new HashMap<String,HashMap<String,URL>>();
	
	private final static List<ResourcePool> resourcePools;
	static {
		Properties jiopiConfig = Config.getJiopiProperties();
		ArrayList<ResourcePool> resourcePoolList = new ArrayList<ResourcePool>();
		HashMap<String,HashMap<String,String>> resourcePoolMap = new HashMap<String,HashMap<String,String>>();
		Enumeration<Object> configNames = jiopiConfig.keys();
		while(configNames.hasMoreElements()){
			String configName = (String) configNames.nextElement();
			if(configName.startsWith(JiopiConfigConstants.RESOURCEPOOL_PREFIX)){
				String[] resourcePoolConfig = configName.substring(JiopiConfigConstants.RESOURCEPOOL_PREFIX.length()).split("\\.");
				if(resourcePoolConfig.length!=2) logger.warn(configName +" is not a correct resource pool setting.");
				String resourcePoolName = resourcePoolConfig[0];
				if(resourcePoolName.length()==0){
					logger.warn("resource pool name can't be empty!");
					continue;
				}
				String dataName = resourcePoolConfig[1];
				HashMap<String,String> resourcePoolData = resourcePoolMap.get(resourcePoolName);
				if(resourcePoolData == null){
					resourcePoolData = new HashMap<String,String>();
					resourcePoolMap.put(resourcePoolName, resourcePoolData);
				}
				if(JiopiConfigConstants.RESOURCEPOOL_PRI.equals(dataName)) resourcePoolData.put(JiopiConfigConstants.RESOURCEPOOL_PRI, jiopiConfig.getProperty(configName));
				else if(JiopiConfigConstants.RESOURCEPOOL_PATH.equals(dataName)) resourcePoolData.put(JiopiConfigConstants.RESOURCEPOOL_PATH, jiopiConfig.getProperty(configName));
				else if("username".equals(dataName)) resourcePoolData.put("username", jiopiConfig.getProperty(configName));
				else if("password".equals(dataName)) resourcePoolData.put("password", jiopiConfig.getProperty(configName));
			}
		}
		for(String poolName:resourcePoolMap.keySet()){
			HashMap<String,String> poolData = resourcePoolMap.get(poolName);
			
			String poolPath = poolData.get(JiopiConfigConstants.RESOURCEPOOL_PATH);
			if(poolData.get(JiopiConfigConstants.RESOURCEPOOL_PATH) ==null){
				logger.warn(poolName+" is not a correct pool setting,path is required");
				continue;
			}
			int pri = -1;
			String poolPri = poolData.get(JiopiConfigConstants.RESOURCEPOOL_PRI);
			if(poolPri!=null){
				try{
					pri = Integer.parseInt(JiopiConfigConstants.RESOURCEPOOL_PRI_DEFAULT);
					pri = Integer.parseInt(poolPri);
				}catch(Exception e){
					logger.warn(e);
				}
			}
			UsernamePasswordCredentials creds = null;
			String poolUsername = poolData.get("username");
			String poolPassword = poolData.get("password");
			if(poolUsername!=null && poolPassword!=null){
				creds = new UsernamePasswordCredentials(poolUsername,poolPassword);
			}
			resourcePoolList.add(new ResourcePool(poolName,pri,poolPath,creds));
		}
		Collections.sort(resourcePoolList);
		//初始化ResourcePools
		resourcePools = Collections.unmodifiableList(resourcePoolList);
	}
	
	@SuppressWarnings("unchecked")
	protected ResourcePool(String name,int pri,String path,UsernamePasswordCredentials creds){
		this.name = name;
		this.pri = pri;
		this.creds = creds;
		this.poolCacheDir = FileUtil.confirmDir(FileUtil.joinPath( ResourceUtil.getProgramDir(),KernelConstants.RESOURCE_POOLS_DIR,name),true);
		//解析Path
		if(path.endsWith(".xml")){//配置文件
			isLocal = false;
			String moduleURL = null;
			String blueprintURL = null;
			try{
				URI resourcePoolURI = new URI(path);
				File configFile = RemoteFileManager.getRemoteFile(resourcePoolURI.toURL(), poolCacheDir,this.creds);
				if(configFile.isFile()){
					SAXReader reader = new SAXReader();
					Document doc = reader.read(configFile);
					Element resourcepools = doc.getRootElement();
					Element blueprints = resourcepools.element("blueprints");
					Element modules = resourcepools.element("modules");
					
					blueprintURL = blueprints.attributeValue("url-format");
					blueprintURL = blueprintURL.replaceAll(BLUEPRINT_NAME, "_______BLUEPRINT_NAME_______");
					URI blueprintURI = resourcePoolURI.resolve(blueprintURL);
					blueprintURL = blueprintURI.toString();
					blueprintURL = blueprintURL.replaceAll("_______BLUEPRINT_NAME_______", BLUEPRINT_NAME);
					
					moduleURL = modules.attributeValue("url-format");
					moduleURL = moduleURL.replaceAll(MODULE_NAME, "_______MODULE_NAME_______");
					URI moduleURI = resourcePoolURI.resolve(moduleURL);
					moduleURL = moduleURI.toString();
					moduleURL = moduleURL.replaceAll("_______MODULE_NAME_______", MODULE_NAME);
					
					//common-libs
					Iterator<Element> commonlibsElement = resourcepools.elementIterator("commonlib");
					while(commonlibsElement.hasNext()){
						Element commonlibElement = commonlibsElement.next();
						String group = commonlibElement.attributeValue("group");
						String baseUrl = commonlibElement.attributeValue("base-url");
						if(group == null){
							logger.warn("Common Lib's group name must set");
							continue;
						}
						URI commonLibPath = resourcePoolURI;
						if(baseUrl!=null)commonLibPath = resourcePoolURI.resolve(baseUrl);
						//System.out.println("ResourcePool 169:common lib "+group+" : "+commonLibPath);
						HashMap<String,URL> resources = new HashMap<String,URL>();
						this.commonLibs.put(group, resources);
						Iterator<Element> resourcesElement = commonlibElement.elementIterator("resource");
						while(resourcesElement.hasNext()){
							Element resourceElement = resourcesElement.next();
							String resource = resourceElement.getTextTrim();
							URL resourceURL = commonLibPath.resolve(resource).toURL();
							String fileName = new File(resourceURL.getFile()).getName();
							resources.put(fileName,resourceURL);
						}
					}
				}else{
					logger.warn("can't get config file :"+path);
				}
			}catch(Exception e){
				logger.error("parse resource path error "+path,e);
			}
			this.modulePath = moduleURL;
			this.blueprintPath = blueprintURL;
		}else{
			isLocal = true;
			this.blueprintPath = FileUtil.joinPath(path,KernelConstants.BLUEPRINTS_DIR);
			this.modulePath = FileUtil.joinPath(path,KernelConstants.MODULES_DIR);
			//common-libs
			try{
				URI rootPath = new URI(path);
				if(rootPath.getScheme().equals("file")){
					URI commonlibs = rootPath.resolve(FileUtil.correctURIDirPath(KernelConstants.COMMON_LIB_DIR));
					File commonlibsDir = new File(commonlibs.toURL().getFile());
					File[] groups = commonlibsDir.listFiles();
					for(File group:groups){
						if(group.isDirectory()){
							String groupName = group.getName();
							HashMap<String,URL> resources = new HashMap<String,URL>();
							this.commonLibs.put(groupName, resources);
							File[] jars = group.listFiles();
							for(File jar:jars){
								URL resourceURL = FileUtil.toURL(jar.getAbsolutePath());
								String fileName = jar.getName();
								resources.put(fileName,resourceURL);
							}
						}
					}
				}else{
					logger.warn("only local file dir can be local :"+path);
				}
			}catch(Exception e){
				logger.warn("parse resource path error "+path,e);
			}
			
		}
	}
	
	protected static Repository.CommonJar getCommonJarByFileName(String fileName){
		for(ResourcePool pool:resourcePools){
			for(String group:pool.commonLibs.keySet()){
				HashMap<String,URL> groupCommonLib = pool.commonLibs.get(group);
				URL jarURL = groupCommonLib.get(fileName);
				if(jarURL!=null){
					//download remote file
					String groupDir = FileUtil.joinPath(pool.poolCacheDir.getPath(),KernelConstants.COMMON_LIB_DIR,group);
					File commonLibCacheDir = FileUtil.confirmDir(groupDir, true);
					if(commonLibCacheDir.isDirectory()){
						File localFile = RemoteFileManager.getRemoteFile(jarURL, commonLibCacheDir,pool.creds);
						if(localFile.isFile()){
							jarURL = FileUtil.toURL(localFile.getPath());
						}
					}else{
						logger.warn("create local dir error "+groupDir+", load remote jar without local cache");
					}
					return new Repository.CommonJar(pool.name,group,jarURL);
				}
			}
		}
		return null;
	}
	
	/**
	 * 通过给定moduleName给出模块ModuleResource,如果查找不到,返回Null
	 * @param moduleName
	 * @param type 资源类型
	 * @see Repository#BLUEPRINT
	 * @see Repository#MODULE
	 * @return
	 */
	protected static ModuleResource getModuleResource(NameVersion moduleName,int type){
		ModuleResource mr = null;
		try{
			URI moduleURI = new URI(moduleName.name);
			String module = moduleURI.getPath();
			if(module.charAt(0)=='/') module = module.substring(1);
			for(ResourcePool pool:resourcePools){
				String poolPath = null;
				String _NAME = null;
				String _name = null;
				switch(type){
					case Repository.BLUEPRINT :
						poolPath = pool.blueprintPath;
						_NAME = BLUEPRINT_NAME;
						_name = KernelConstants.BLUEPRINTS_DIR;
						break;
					case Repository.MODULE:
						poolPath = pool.modulePath;
						_NAME = MODULE_NAME;
						_name = KernelConstants.MODULES_DIR;
						break;
				}
				if(poolPath.endsWith(".xml")||pool.isLocal){
					ModuleConfig moduleConfig = Repository.getModuleConfig(pool.name+"-"+module,null,null,pool.isLocal,pool.creds);
					if(moduleConfig==null){
						if(!pool.isLocal){
							String moduleConfigFilePath = poolPath.replaceAll(_NAME, module);
							URI moduleConfigFileURI = new URI(moduleConfigFilePath);
							String moduleProgramPath = FileUtil.joinPath(ResourceUtil.getProgramDir(),KernelConstants.RESOURCE_POOLS_DIR,pool.name , _name ,module);
							String moduleConfigCacheDir = FileUtil.joinPath(moduleProgramPath,"/config/");
							boolean fileExists =  ResourceUtil.isResourceExists(moduleConfigFileURI,new File(moduleConfigCacheDir),pool.creds);
							if(fileExists){
								moduleConfig = Repository.getModuleConfig(pool.name+"-"+_name+"-"+module,moduleConfigFileURI.toURL(),moduleProgramPath,pool.isLocal,pool.creds);
							}
							else{
								logger.warn(moduleConfigFileURI.toString()+" is not exists");
							}
						}else{
							poolPath = FileUtil.joinPath(poolPath,module);
							moduleConfig = Repository.getModuleConfig(pool.name+"-"+_name+"-"+module,FileUtil.toURL(poolPath),null,pool.isLocal,pool.creds);
						}
					}
					if(moduleConfig!=null){
						ModuleConfig.Release release = moduleConfig.getRelease(moduleName);
						if(release!=null){
							URL[] releaseResources = release.getResources();
							if(releaseResources!=null){
								return new ModuleResource(new NameVersion(pool.name+"-"+module,release.version),releaseResources,type);
							}
						}
					}
				}
			}
		}catch(Exception e){
			logger.error(e);
		}
		return mr;
	}
	
	/**
	 * 这里的比较是 9<8 以便 自动排序时高优先级在上
	 */
	public int compareTo(ResourcePool o) {
		int thisVal = this.pri;
		int anotherVal = o.pri;
		return (thisVal<anotherVal ? 1 : (thisVal==anotherVal ? 0 : -1));
	}
	
	public String toString(){
		return "[ResourcePool name="+name+",pri="+pri+",blueprint="+blueprintPath+",module="+modulePath+"]";
	}
	
	
	
}